////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.charts.chartClasses
{

import flash.display.DisplayObject;
import flash.geom.Point;
import flash.geom.Rectangle;
import mx.charts.LinearAxis;
import mx.charts.styles.HaloDefaults;
import mx.core.UIComponent;
import mx.graphics.SolidColor;
import mx.graphics.Stroke;
import mx.styles.CSSStyleDeclaration;
import mx.core.mx_internal;

use namespace mx_internal;

/**
 *  The PolarChart control serves as base class for circular charts
 *  based in polar coordinates.
 *  
 *  
 *  A chart's minimum size is 20,20 pixels. <br/>
 *  A chart's maximum size is unbounded. <br/>
 *  A chart's preferred size is 400,400 pixels. <br/>
 *  
 *  @see mx.charts.CategoryAxis
 *  @see mx.charts.LinearAxis 
 *  @see mx.charts.chartClasses.ChartBase
 */
public class PolarChart extends ChartBase
{
    include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class initialization
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private static var stylesInited:Boolean = initStyles();	
	
	/**
	 *  @private
	 */
	private static function initStyles():Boolean
	{
		HaloDefaults.init();

		var polarChartStyle:CSSStyleDeclaration =
			HaloDefaults.createSelector("PolarChart");

		polarChartStyle.defaultFactory = function():void
		{
			this.dataTipRenderer = DataTip;
			this.fill = new SolidColor(0xFFFFFF, 0);
			this.calloutStroke = new Stroke(0x888888,2);			
			this.fontFamily = "Arial";
			this.fontSize = 10;
		}

		return true;
	}

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 */
	public function PolarChart()
	{
		super();

		transforms = [ new PolarTransform() ];
		
		var aa:LinearAxis = new LinearAxis();
		aa.autoAdjust = false;
		angularAxis = aa;
	}

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 */
	private var axisLayoutDirty:Boolean = true;
	
	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

    //----------------------------------
	//  angularAxis
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the angularAxis property.
	 */
	private var _angularAxis:IAxis;

	[Inspectable(category="Data")]
	
	/**
	 *  The axis object used to map data values to an angle
	 *  between 0 and 2 * PI.
	 *  By default, this is a linear axis with the <code>autoAdjust</code>
	 *  property set to <code>false</code>.
	 *  So, data values are mapped uniformly around the chart.
	 */
	public function get angularAxis():IAxis
	{
		return _angularAxis;
	}	
	
	/**
	 *  @private
	 */
	public function set angularAxis(value:IAxis):void
	{
		_transforms[0].setAxis(PolarTransform.ANGULAR_AXIS, value);
		_angularAxis = value;

		invalidateData();
	}	

    //----------------------------------
	//  radialAxis
    //----------------------------------

	[Inspectable(category="Data")]
	
	/**
	 *  The axis object used to map data values to a radial distance
	 *  between the center and the outer edge of the chart.
	 *  By default, this is a linear axis with the <code>autoAdjust</code>
	 *  property set to <code>false</code>.
	 *  So, data values are  mapped uniformly from the inside
	 *  to the outside of the chart 
	 */
	public function get radialAxis():IAxis
	{
		return _transforms[0].getAxis(PolarTransform.RADIAL_AXIS);
	}	
	
	/**
	 *  @private
	 */
	public function set radialAxis(value:IAxis):void
	{
		_transforms[0].setAxis(PolarTransform.RADIAL_AXIS, value);

		invalidateData();
	}	

	//--------------------------------------------------------------------------
	//
	//  Overriden methods: UIComponent
	//
	//--------------------------------------------------------------------------

	/**
	 *  @inheritDoc 
	 */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
	{
		super.updateDisplayList(unscaledWidth, unscaledHeight);
		
		// Force the range to update any automatic mappings.
		_angularAxis.getLabelEstimate();

		var paddingLeft:Number = getStyle("paddingLeft");
		var paddingRight:Number = getStyle("paddingRight");
		var paddingTop:Number = getStyle("paddingTop");
		var paddingBottom:Number = getStyle("paddingBottom");

		var rcElements:Rectangle = new Rectangle(
			paddingLeft, paddingTop,
			unscaledWidth - paddingLeft - paddingRight,
			unscaledHeight - paddingTop - paddingBottom);
									 
		var i:int;
		
		for (i = 0; i < _transforms.length; i++)
		{
			_transforms[i].setSize(rcElements.width,rcElements.height);
		}

		var n:int = allElements.length;
		for (i = 0; i < n; i++)
		{
			var c:DisplayObject = allElements[i];
			if (c is UIComponent)
			{
				(c as UIComponent).setActualSize(rcElements.width,
												 rcElements.height);
			}
			else 
			{
				c.width = rcElements.width;
				c.height = rcElements.height;
			}
		}

		if (_seriesHolder.mask != null)
		{
			_seriesHolder.mask.width = rcElements.width;
			_seriesHolder.mask.height = rcElements.height;
		}

		if (_backgroundElementHolder.mask != null)
		{
			_backgroundElementHolder.mask.width = rcElements.width;
			_backgroundElementHolder.mask.height = rcElements.height;
		}

		if (_annotationElementHolder.mask != null)
		{
			_annotationElementHolder.mask.width = rcElements.width;
			_annotationElementHolder.mask.height = rcElements.height;
		}

		_seriesHolder.move(rcElements.left, rcElements.top);
		_backgroundElementHolder.move(rcElements.left, rcElements.top);
		_annotationElementHolder.move(rcElements.left, rcElements.top);

		axisLayoutDirty = false;
	}

	//--------------------------------------------------------------------------
	//
	//  Overriden methods: ChartBase
	//
	//--------------------------------------------------------------------------

	/**
	 *  @inheritDoc 
	 */
	override public function dataToLocal(...dataValues):Point
	{
		var data:Object = {};
		
		var da:Array = [ data ];
		
		var len:int = dataValues.length;
		
		if (len > 0)
		{
			data["d0"] = dataValues[0];
			
			_transforms[0].getAxis(PolarTransform.ANGULAR_AXIS).
				mapCache(da, "d0", "v0");
		}
		
		if (len > 1)
		{
			data["d1"] = dataValues[1];
			
			_transforms[0].getAxis(PolarTransform.RADIAL_AXIS).
				mapCache(da, "d1", "v1");			
		}
		
		_transforms[0].transformCache(da, "v0", "s0", "v1", "s1");
		
		return new Point(_transforms[0].origin.x +
						 Math.cos(data.s0) * data.s1,
						 _transforms[0].origin.y -
						 Math.sin(data.s0) * data.s1);
	}
	
	/**
	 *  @inheritDoc 
	 */
	override public function localToData(v:Point):Array
	{
		var dx:Number = v.x - _transforms[0].origin.x;
		var dy:Number = v.y - _transforms[0].origin.y;
		
		var a:Number = calcAngle(dx,dy);
		
		var r:Number = Math.sqrt(dx * dx + dy * dy);		
		
		var values:Array = _transforms[0].invertTransform(a, r);
		return values;
	}

	/**
	 *  @inheritDoc 
	 */
	override protected function get dataRegion():Rectangle
	{
		return getBounds(this);
	}

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private function calcAngle(x:Number, y:Number):Number
	{
		const twoMP:Number = Math.PI * 2;

		var angle:Number;
		
		var at:Number = Math.atan(-y / x);
		
		if (x < 0)
			angle = at + Math.PI;
		else if (y < 0)
			angle = at;
		else
			angle = at + twoMP;

		return angle;
	}
}

}
